/**
 * @fileOverview Different util classes
 * @requires app
 */

(function() {
	/** @lends app */
	$.extend(app, {
		/**
		 * @property {Boolean} Debug mode flag
		 * @description Debug mode flag
		 */
		debugMode : true,
		
		/**
		 * This method can extend given object with new properties
		 *
		 * @param {Object} obj Object to extend
		 * @param {Object} [extenders...] Objects which properties will be added to first object
		 * @returns {Object} Extended object
		 */
		extend : function(obj) {
			var args = Array.prototype.slice.call(arguments, 1);
			
			for (var i = 0; i < args.length; i++) {
				for (var prop in args[i]) {
					if (args[i][prop] !== void 0) {
						obj[prop] = args[i][prop];
					}
				}
			}
			
			return obj;
		},
		
		/**
		 * Print a debug messages to log
		 *
		 * @param {Object} [items...] Items to print to log
		 * @returns {app} Main namespace
		 */
		debug : function() {
			if (this.debugMode) {
				//console.log.apply(this, Array.prototype.slice.call(arguments));
				console.log(arguments);
			}
			
			return this;
		}
	});

	/* ************************************************************************************* */
	
	/**
	 * Basic constructor function
	 * @function
	 */
	var ctor = function(){};

	/**
	 * Function that allows to inherit object from another one and add new properties to newly created class
	 *
	 * @function
	 * @param {Object} parent Parent class
	 * @param {Object} [protoProps] List of properties that will be added to new class prototype
	 * @param {Object} [staticProps] List of properties that will be added to new class object itself
	 * @returns {Object} New class
	 */
	var inherit = function(parent, protoProps, staticProps) {
		var child;

		if (protoProps && protoProps.hasOwnProperty('constructor')) {
			child = protoProps.constructor;
		} else {
			/** @ignore */
			child = function() {
				return parent.apply(this, arguments);
			};
		}

		app.extend(child, parent);

		ctor.prototype = parent.prototype;
		child.prototype = new ctor();

		if (protoProps) {
			app.extend(child.prototype, protoProps);
		}

		if (staticProps) {
			app.extend(child, staticProps);
		}

		child.prototype.constructor = child;
		child.__super__ = parent.prototype;

		return child;
	};
	
	/* ************************************************************************************* */
	
	 /**
	  * Constructs a new instance of this class.<br />
	  * Calls {@link #init} method on each instance initialization and passes all constructor arguments to it.
	  * @class Basic class for other classes that should be extendable
	  * @param {Object} [options={}] Options list can be provided to initialize new instance
	  *
	  * @property {Object} handlers List of event handlers for this object
	  */
	app.Basic = function(options) {
		this.handlers = {};
		
		this.init.apply(this, Array.prototype.slice.call(arguments));
	};

	/**
	 * Extend prototype of app.Extendable class with basic methods
	 * @lends app.Basic.prototype
	 */
	app.extend(app.Basic.prototype, {
		/**
		 * Basic initialization method; should be overrided in inherited classes
		 *
		 * @param {Object} options Initialization options
		 */
		init : function(options) {},
		
		/**
		 * Trigger an event
		 *
		 * @param {String} eventName Event name
		 * @param {Object} [params...] Additional arguments that will be passed to event handlers
		 * @returns {Object} Current instance
		 */
		trigger : function(eventName) {
			if (this.handlers[eventName]) {
				for (var i = 0; i < this.handlers[eventName].length; i++) {
					this.handlers[eventName][i].apply(this, Array.prototype.slice.call(arguments, 1));
				}
			}
			
			return this;
		},
		
		/**
		 * Bind a handler to specific event
		 *
		 * @param {Object} [handlers] Hash of event names and handlers; key is an event name and value is a handler function
		 * @param {String} [eventName] Event name
		 * @param {Function} [handler] Event handler function
		 * @returns {Object} Current instance
		 */
		bind : function() {
			if (arguments.length == 1) {
				for (var eventName in arguments[0]) {
					this.handlers[eventName] || (this.handlers[eventName] = []);
			
					this.handlers[eventName].push(arguments[0][eventName]);
				}
			}
			else {
				this.handlers[arguments[0]] || (this.handlers[arguments[0]] = []);
				
				this.handlers[arguments[0]].push(arguments[1]);
			}
			
			return this;
		},
		
		/**
		 * Call some method of parent class
		 *
		 * @param {String} method Method of parent class
		 * @param {any} [arguments...] Any other arguments that will be passed to parent method
		 * @returns {any} Result of parent method
		 */
		callParent : function(method) {
			return this.constructor.__super__[method].apply(this, Array.prototype.slice.call(arguments, 1));
		}
	});
	
	/** @lends app.Basic */

	/**
	 * Add extend capatibilities to app.Basic class and all classes that will be inherited from it
	 *
	 * @static
	 * @param {Object} protoProps List of properties that will be added to prototype
	 * @param {Object} classProps List of properties that will be added to object itself
	 * @returns {Object} New class inherited from current one
	 */
	app.Basic.extend = function(protoProps, classProps) {
		var child = inherit(this, protoProps, classProps);
		
		child.extend = this.extend;
		
		return child;
	}
	
	/* ************************************************************************************* */

	app.Collection = app.Basic.extend({
		/** @lends app.Collection.prototype */

		/**
		 * Collection can be initialized with start items
		 * @constructs
		 * @extends app.Basic
		 * @borrows app.Basic.extend as extend
		 *
		 * @property {Object[]} items Array of items in this collection
		 *
		 * @param {Object[]} items Array of start items
		 */
		init : function(items) {
			this.items = app.Util.isArray(items) && items || [];
		},
		
		/**
		 * Add new item to collection
		 *
		 * @param {Object} item New item of any type
		 * @returns {app.Collection} Current collection object instance
		 */
		add : function(item) {
			this.items.push(item);
			
			return this;
		},
		
		/**
		 * Set new item at a specific index
		 *
		 * @param {Number} index Item index
		 * @param {Object} New item of any type
		 * @returns {app.Collection} Current collection object instance
		 */
		set : function(index, item) {
			this.items[index] = item;
			
			return this;
		},
		
		/**
		 * Get item at a specific index from current collection
		 *
		 * @param {Number} index Item index
		 * @returns {Object} Item
		 */
		get : function(index) {
			return this.items[index];
		},
		
		/**
		 * Get random item from the collection
		 *
		 * @returns {Object} item
		 */
		getRandom : function() {
			return this.get(Math.floor(Math.random() * this.size()));
		},
		
		/**
		 * Remove item at a specific index from current collection
		 *
		 * @param {Number} index Item index
		 * @returns {app.Collection} Current collection object instance
		 */
		remove : function(index) {
			delete this.items[index];
			
			return this;
		},
		
		/**
		 * Return the number of items in current collection
		 *
		 * @returns {Number} Number of items in current collection
		 */
		size : function() {
			return this.items.length;
		},
		
		/**
		 * Filter the current collection using a callback function
		 *
		 * @param {Function} callback Callback function
		 * @param {any} [callback.item] Argument of callback function; item that is currently being processed
		 * @returns {app.Collection} New instance of current class
		 */
		filter : function(callback) {
			var items = [];
			
			if (callback) {
				for (var i = 0; i < this.size(); i++) {
					callback(this.get(i)) && items.push(this.get(i));
				}
			}
			
			return new this.constructor(items);
		}
	});
	
	/* ************************************************************************************* */
	
	app.Manager = app.Basic.extend({
		/** @lends app.Manager.prototype */
		
		/**
		 * Basic class for all Manager factory classes
		 * @constructs
		 * @borrows app.Basic.extend as extend
		 * @extends app.Basic
		 *
		 * @property {Object} cache Cache of types created
		 * @property {Object} handledClass Class from which this manager will create instances
		 *
		 * @param {Object} handledClass Class object that this manager will handle
		 */
		init : function(handledClass) {
			this.cache = {};
			this.handledClass = handledClass;
		},
		
		/**
		 * Create new item type and save it in cache
		 *
		 * @param {Object|Array} options Configuration hash or an array of configurations
		 * @returns {app.Manager} Current class instance
		 */
		createType : function(options) {
			var id, base;
			
			if (!options) {
				return this;
			}
			
			if (options.length) {
				for (var i = 0; i < options.length; i++) {
					this.createType(options[i]);
				}
				
				return this;
			}
			
			id = options.id;
			delete options.id;
			
			base = options.base;
			delete options.base;
			
			this.cache[id] = $.extend(true, {}, this.cache[base], options);
			
			return this;
		},
		
		/**
		 * Create new instance of class that current manager is handling
		 *
		 * @param {String} type Item type ID
		 * @param {Object} options Configuration that will extend basic options and initialize the created item
		 * @returns {Object} Item instance
		 */
		create : function(type, options) {
			options = app.extend({}, this.cache[type], options);
			
			return new this.handledClass(options);
		}
	});
	
	/* ************************************************************************************* */
	
	/**
	 * Namespace that holds different utility methods
	 * @namespace
	 */
	app.Util = {
		/**
		 * Check if given variable is a function
		 *
		 * @param {any} obj Variable to check
		 * @returns {Boolean} <b>true</b> if variable is a function
		 */
		isFunction : function(obj) {
			return !!(obj && obj.call);
		},
		
		/**
		 * Check if variable is an array
		 *
		 * @param {any} obj Variable to check
		 * @returns {Boolean} <b>true</b> if variable is an array
		 */
		isArray : function(obj) {
			return !!(obj && obj.pop);
		},
		
		/**
		 * Check if variable is a boolean
		 *
		 * @param {any} obj Variable to check
		 * @returns {Boolean} <b>true</b> if variable is a boolean
		 */
		isBoolean : function(obj) {
			return obj === true || obj === false;
		},
		
		/**
		 * Check if variable is a numeric value
		 *
		 * @param {any} obj Variable to check
		 * @returns {Boolean} <b>true</b> if variable is a number
		 */
		isNumber : function(obj) {
			return isFinite(obj);
		},
		
		/**
		 * Get a random number in given interval
		 *
		 * @example
		 * app.Util.random()	// Floating point number from 0 to 1 (like native Math.random())
		 * app.Util.random(3)	// Integer from 0 to 3
		 * app.Util.random(1, 5)	// Integer from 1 to 5
		 *
		 * @param {Number} [min=0] Minimum boundary
		 * @param {Number} [max=1] Maximum boundary
		 * @returns {Number} Number in interval
		 */
		random : function() {
			var min = 0,
				max = 0;
			
			if (arguments.length == 1) {
				return Math.floor(this.random() * (arguments[0] + 1));
			}
			else if (arguments.length == 2) {
				return this.random(arguments[1] - arguments[0]) + arguments[0];
			}
			else {
				return Math.random();
			}
		},
		
		/**
		 * Returns formatted string.<br />
		 * Currently supported placeholders:
		 * <ul>
		 * <li><b>%s</b> - insert value as a string</li>
		 * <li><b>%i</b> - insert value as an integer</li>
		 * <li><b>%f</b> - insert value as a float</li>
		 * </ul>
		 * Placeholders can also be numbered, e.g. <b>%s{2}</b> will be replaced with string representation of argument with index 2 (index is starting from 0) and so on.
		 *
		 * @example
		 * // First: string 1, first as Integer: NaN, next: 2, float: 2.5
		 * app.Util.format('First: %s, first as Integer: %i{0}, next: %i, float: %f{1}', 'string 1', 2.5);
		 *
		 * @param {String} str String template with placeholders
		 * @param {any} [arguments...] Any amount of arguments that will be used in template
		 * @returns {String} Formatted string
		 */
		format : function(str) {
			var args, current = 0;
			
			args = Array.prototype.slice.call(arguments, 1);
			
			str = str.replace(/%(\w)(?:\{(\d+)\})?/g, function(placeholder, type, number) {
				var value;
				
				value = typeof number == 'undefined' ? args[current++] : args[+number];
				
				switch (type) {
					case 'i':
						value = parseInt(value);
						break;
					case 'f':
						value = parseFloat(value);
						break;
					default:
						value = value.toString();
				}
				
				return value;
			});
			
			return str;
		}
	};
	
	/* ************************************************************************************* */
	
	app.Value = app.Basic.extend({
		/** @lends app.Value.prototype */
		
		/**
		 * Class for handling numeric values
		 * @constructs
		 * @extends app.Basic
		 * @borrows app.Basic.extend as extend
		 *
		 * @example
		 * new app.Value(5);	// 5
		 * new app.Value({		// 10%
		 * 	value : 10,
		 * 	min : 0,
		 * 	max : 100,
		 * 	format : '%i%'
		 * });
		 *
		 * @property {Number} value Holded value
		 * @property {Number} [min=null] Minimum boundary
		 * @property {Number} [max=null] Maximum boundary
		 * @property {String} [format] Output format (when printing formatted value or string representation)
		 *
		 * @param {Number|Object} value Initial value
		 * @param {Number} [value.value] Initial value
		 * @param {Number} [value.min] Minimum value
		 * @param {Number} [value.max] Maximum value
		 * @param {String} [value.format] Output format
		 */
		init : function(value) {
			if (app.Util.isNumber(value)) {
				this.min = null;
				this.max = null;
				this.format = '';
				
				this.set(value);
			}
			else {
				if (value.min > value.max) {
					value.max = value.min;
				}
			
				this.min = value.min || null;
				this.max = value.max || null;
				this.format = value.format || '';
				
				if (this.max < this.min) {
					this.max = this.min;
				}
				
				this.set(value.value);
			}
		},
		
		/**
		 * Set new value
		 *
		 * @param {Number} value New value
		 * @returns {app.Value} Current instance
		 */
		set : function(value) {
			this.value = value;
			
			(this.min !== null && value < this.min) && (this.value = this.min);
			(this.max !== null && value > this.max) && (this.value = this.max);
			
			return this;
		},
		
		/**
		 * Get current value
		 *
		 * @returns {Number} Current value
		 */
		get : function() {
			return this.value;
		},
		
		/**
		 * Get formatted value
		 *
		 * @return {String} Formatted value
		 */
		getFormatted : function() {
			return this.format ? app.Util.format(this.format, this.get()) : this.get();
		},
		
		/**
		 * Modify current value
		 *
		 * @param {Number} value Value modifier
		 * @returns {app.Value} Current instance
		 */
		modify : function(value) {
			value = app.Util.isNumber(value) ? (this.get() + value) : (this.get() / 100 * parseFloat(value));
		
			this.set(value);
			
			return this;
		},
		
		/**
		 * String representation of value
		 *
		 * @returns {String} Value
		 */
		toString : function() {
			return this.getFormatted();
		}
	});
	
	/* ************************************************************************************* */
	
	app.ValueInterval = app.Value.extend({
		/** @lends app.ValueInterval.prototype */
		
		/**
		 * Numeric value, that can return a random number between min and max
		 * @constructs
		 * @extends app.Value
		 * @borrows app.Basic.extend as extend
		 *
		 * @property {Object} value Holded value interval
		 *
		 * @param {Number} min Minimum value
		 * @param {Number} max Maximum value
		 * @param {Object} [options] Additional options
		 * @param {Number} [options.min=null] Minimum boundary
		 * @param {Number} [options.max=null] Maximum boundary
		 * @param {String} [options.format=''] Output format
		 */
		init : function(min, max, options) {
			options || (options = {});
			
			this.min = options.min || null;
			this.max = options.max || null;
			this.format = options.format || '';
			
			this.set(min, max);
		},
		
		/**
		 * Get a random value from holded interval
		 *
		 * @returns {Number} Random value
		 */
		get : function() {
			return app.Util.random(this.value.min, this.value.max);
		},
		
		/**
		 * Set a new interval
		 *
		 * @param {Number} min Minimum value
		 * @param {Number} max Maximum value
		 * @returns {app.ValueInterval} Current instance
		 */
		set : function(min, max) {
			(this.min !== null && min < this.min) && (min = this.min);
			(this.max !== null && max < this.max) && (max = this.max);
			
			this.value.min = min;
			this.value.max = max;
			
			return this;
		}
	});
	
	/* ************************************************************************************* */
	
	/**
	 * Class for creating periodical stuff
	 * @class Class for creating periodical stuff
	 * @extends app.Basic
	 * @borrows app.Basic.extend as extend
	 *
	 * @property {Number} [duration=0] Timer duration in seconds
	 * @property {Number} [interval=1] Interval between each iteration
	 * @property {Function} handler Function that will be executed on each iteration
	 * @property {Function} condition Function that should return <b>true</b> if timer will stop
	 * @property {Number} iterations Number of timer iterations
	 * @property {Number} limit Maximum number of iterations before timer will stop
	 * @property {Number} timer Timer descriptor
	 */
	app.Timer = app.Basic.extend({
		/** @lends app.Timer.prototype */
		
		/**
		 * Timer initialization
		 *
		 * @param {Object} options Timer options
		 * @param {Number} [options.duration=0] Timer duration in seconds
		 * @param {Number} [options.interval=1] Interval between each iteration
		 * @param {Function} options.handler Function that will be executed on each iteration
		 * @param {Function} [options.condition] Function that should return <b>true</b> if timer will stop
		 * @param {Boolean} [options.start=false] Start the timer immediately
		 */
		init : function(options) {
			var that = this;
			
			this.duration = options.duration || 0;
			this.interval = options.interval || 1;
			this.handler = options.handler || function() {};
			this.condition = options.condition || function() {
				return this.iterations >= this.limit;
			};
			this.iterations = 0;
			this.limit = Math.floor(this.duration / this.interval);
			this.timer = null;
			
			// Check if we need to start the timer
			if (!!options.start) {
				this.start();
			}
		},
		
		/**
		 * Start timer.<br />Triggers the {@link #event:start} event.
		 */
		start : function() {
			var that = this;
			
			this.trigger('start');
		
			this.timer = setInterval(function() {
				that.nextIteration();
			}, this.interval * 1000);
		},
		
		/**
		 * Stop timer.<br />Triggers {@link #event:stop} event.
		 */
		stop : function() {
			this.trigger('stop');
			
			clearInterval(this.timer);
		},
		
		/**
		 * Run the next iteration of current timer.<br />Triggers {@link #event:next} event.
		 */
		nextIteration : function() {
			this.iterations++;
			
			this.trigger('next', this.iterations);
			
			this.handler && this.handler();
			
			if (this.condition()) {
				this.stop();
			}
		}
	});
	
	/**
	 * Fired on timer start
	 *
	 * @name app.Timer#start
	 * @event
	 */
	
	/**
	 * Fired on timer stop
	 *
	 * @name app.Timer#stop
	 * @event
	 */
	
	/**
	 * Fired on each iteration of the timer
	 *
	 * @name app.Timer#next
	 * @event
	 * @param {Number} iteration Current iteration of the timer
	 */
})();